<template>
  <div class="container">
    <highlight-panel :dataSource="dataSource" :visible="panelVisible" @change="handleChange" @close="hideKeywordsPanel" />
    <highlight-bar @scrollTo="handleScrollTo" :map="map" />
    <div class="hl-content" ref="scroll">
      <div style="padding: 24px;position: relative;" ref="html" v-html="computedHtml"></div>
    </div>
  </div>
</template>

<script>
import HighlightPanel from './HighlightPanel.vue'
import HighlightBar from './HighlightBar.vue'
import { highlight, highdense } from 'html-replacer'
import { debounce } from 'lodash'
import { message } from 'ant-design-vue'
export default {
  name: 'Highlight',
  components: {
    HighlightPanel,
    HighlightBar
  },
  data() {
    return {
      map: null,
      dataSource: [],
      panelVisible: false,
      html: `<p>Vue.js 是什么</p>
        <p>Vue (读音 /vjuː/，类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是，Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层，不仅易于上手，还便于与第三方库或既有项目整合。另一方面，当与现代化的工具链以及各种支持类库结合使用时，Vue 也完全能够为复杂的单页应用提供驱动。</p>
        <p>如果你想在深入学习 Vue 之前对它有更多了解，我们制作了一个视频，带您了解其核心概念和一个示例工程。</p>
        <p>如果你已经是有经验的前端开发者，想知道 Vue 与其它库/框架有哪些区别，请查看对比其它框架。</p>
        <p>起步</p>
        <p>官方指南假设你已了解关于 HTML、CSS 和 JavaScript 的中级知识。如果你刚开始学习前端开发，将框架作为你的第一步可能不是最好的主意——掌握好基础知识再来吧！之前有其它框架的使用经验会有帮助，但这不是必需的。</p>
        <p>声明式渲染</p>
        <p>我们已经成功创建了第一个 Vue 应用！看起来这跟渲染一个字符串模板非常类似，但是 Vue 在背后做了大量工作。现在数据和 DOM 已经被建立了关联，所有东西都是响应式的。我们要怎么确认呢？打开你的浏览器的 JavaScript 控制台 (就在这个页面打开)，并修改 app.message 的值，你将看到上例相应地更新。</p>
        <p>注意我们不再和 HTML 直接交互了。一个 Vue 应用会将其挂载到一个 DOM 元素上 (对于这个例子是 #app) 然后对其进行完全控制。那个 HTML 是我们的入口，但其余都会发生在新创建的 Vue 实例内部。</p>
        <p>这里我们遇到了一点新东西。你看到的 v-bind attribute 被称为指令。指令带有前缀 v-，以表示它们是 Vue 提供的特殊 attribute。可能你已经猜到了，它们会在渲染的 DOM 上应用特殊的响应式行为。在这里，该指令的意思是：“将这个元素节点的 title attribute 和 Vue 实例的 message property 保持一致”。</p>
        <p>如果你再次打开浏览器的 JavaScript 控制台，输入 app2.message = '新消息'，就会再一次看到这个绑定了 title attribute 的 HTML 已经进行了更新。</p>
        <p>组件化应用构建</p>
        <p>组件系统是 Vue 的另一个重要概念，因为它是一种抽象，允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想，几乎任意类型的应用界面都可以抽象为一个组件树：</p>
        <p>尽管这只是一个刻意设计的例子，但是我们已经设法将应用分割成了两个更小的单元。子单元通过 prop 接口与父单元进行了良好的解耦。我们现在可以进一步改进 &lt;todo-item&gt; 组件，提供更为复杂的模板和逻辑，而不会影响到父单元。</p>
        <p>与自定义元素的关系</p>
        <p>你可能已经注意到 Vue 组件非常类似于自定义元素——它是 Web 组件规范的一部分，这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot API 与 is attribute。但是，还是有几个关键差别：</p>
        <p>Web Components 规范已经完成并通过，但未被所有浏览器原生实现。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下，Vue 组件不需要任何 polyfill，并且在所有支持的浏览器 (IE9 及更高版本) 之下表现一致。必要时，Vue 组件也可以包装于原生自定义元素之内。</p>
        <p>Vue 组件提供了纯自定义元素所不具备的一些重要功能，最突出的是跨组件数据流、自定义事件通信以及构建工具集成。</p>
        <p>虽然 Vue 内部没有使用自定义元素，不过在应用使用自定义元素、或以自定义元素形式发布时，依然有很好的互操作性。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。</p>`
    }
  },
  computed: {
    computedHtml() {
      const list = this.dataSource
      const html = this.html
      if (!list) {
        return html
      }
      const highlighters = list.reduce(
        (array, item) => array.concat({ word: item.text, color: item.color }),
        []
      )
      return highlight(html, highlighters)
    }
  },
  watch: {
    dataSource() {
      this.getHighdense()
    }
  },
  mounted() {
    message.info('请使用ctrl + f 组合快捷键呼出关键词搜索面板')
    window.addEventListener('keydown', this.showKeywordsPanel)
    this.debouncedGetHighdense = debounce(this.getHighdense, 1000)
    window.addEventListener('resize', this.debouncedGetHighdense)
  },
  beforeDestroy() {
    window.removeEventListener('keydown', this.showKeywordsPanel)
    window.removeEventListener('resize', this.debouncedGetHighdense)
  },
  methods: {
    handleScrollTo(percent) {
      this.$refs.scroll.scrollTop =
        (this.$refs.html.scrollHeight * Number(percent.replace(/[%]/gi, ''))) /
        100
    },
    getHighdense() {
      this.$nextTick(() => {
        this.map = highdense(this.$refs.html)
      })
    },
    showKeywordsPanel(e) {
      if (e.ctrlKey) {
        if (e.key === 'f') {
          this.panelVisible = !this.panelVisible
          e.preventDefault()
        }
      }
    },
    hideKeywordsPanel() {
      this.panelVisible = false
    },
    handleChange(list) {
      this.dataSource = list
    }
  }
}
</script>

<style lang="less" scoped>
.container {
  position: relative;
  overflow: hidden;
  padding: 0;
}
.hl-content {
  overflow-y: auto;
  height: 100%;
}
</style>